package com.joker.aigc.service.impl;

import cn.hutool.core.util.StrUtil;
import com.alicp.jetcache.Cache;
import com.alicp.jetcache.CacheManager;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.template.QuickConfig;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.joker.aigc.domain.enums.AppHttpCodeEnum;
import com.joker.aigc.exception.BusinessException;
import com.joker.aigc.domain.dto.UserDto;
import com.joker.aigc.domain.dto.UserLoginDto;
import com.joker.aigc.domain.entity.User;
import com.joker.aigc.domain.mapstruct.UserMapstruct;
import com.joker.aigc.mapper.UserMapper;
import com.joker.aigc.service.LoginService;
import com.joker.aigc.utils.JwtUtils;
import com.joker.aigc.utils.RedisKey;
import com.joker.aigc.utils.RedisUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.Async;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.time.Duration;
import java.util.Objects;
import java.util.concurrent.TimeUnit;


@Service
@Slf4j
@Transactional
@RequiredArgsConstructor
public class LoginServiceImpl extends ServiceImpl<UserMapper, User> implements LoginService {


    private final JwtUtils jwtUtils;

    private final UserMapstruct userMapstruct;

    private final PasswordEncoder passwordEncoder;
    /**
     * token过期时间
     */
    private static final Integer TOKEN_EXPIRE_DAYS = 1;
    /**
     * token续期时间
     */
    private static final Integer TOKEN_RENEWAL_DAYS = 2;

    private final CacheManager cacheManager;

    private Cache<Long, String> userCache;

    @PostConstruct
    public void init() {
        QuickConfig qc = QuickConfig.newBuilder("userCache:")
                .expire(Duration.ofSeconds(36000))
                .cacheType(CacheType.LOCAL)
                // 本地缓存更新后，将在所有的节点中删除缓存，以保持强一致性
                .syncLocal(false)
                .build();
        userCache = cacheManager.getOrCreateCache(qc);
    }


    /**
     * 校验token是不是有效
     *
     * @param token
     * @return
     */
    @Override
    public boolean verify(String token) {
        Long uid = jwtUtils.getUidOrNull(token);
        log.info("LoginServiceImpl  从token获取到的 uid-->{}", uid);
        return uid != null;
    }

    @Async
    @Override
    public void renewalTokenIfNecessary(String token) {
        Long uid = jwtUtils.getUidOrNull(token);
        if (Objects.isNull(uid)) {
            return;
        }
        String key = RedisKey.getKey(RedisKey.USER_TOKEN_STRING, uid);
        long expireDays = RedisUtils.getExpire(key, TimeUnit.DAYS);
        if (expireDays == -2) {
            //不存在的key
            return;
        }
        if (expireDays < TOKEN_RENEWAL_DAYS) {
            //小于一天的token帮忙续期
            RedisUtils.expire(key, TOKEN_EXPIRE_DAYS, TimeUnit.DAYS);
        }
    }

    @Override
    public String login(UserLoginDto userLoginDto) {
        String username = userLoginDto.getUsername();
        String password = userLoginDto.getPassword();
        log.info("username:{},password:{}", username, password);
        User user = getBaseMapper().selectOne(new LambdaQueryWrapper<User>().eq(User::getUsername, username));
        if (user == null) {
            throw new BusinessException(AppHttpCodeEnum.ACCOUNT_NOT_EXIST);
        }
        if (!passwordEncoder.matches(password, user.getPassword())) {
            throw new BusinessException(AppHttpCodeEnum.WRONG_PASSWORD);
        }
        Long uid = user.getId();
        String token = userCache.get(uid);
        log.info("LoginServiceImpl  从缓存获取到的 token-->{}", token);
        if (StringUtils.isNotBlank(token)) {
            return token;
        }
        //获取用户token
        token = jwtUtils.createToken(uid);
        userCache.put(uid, token);
        return token;
    }

    @Override
    public String login() {
        // 用作测试
        Long uid = 2L;
        String key = RedisKey.getKey(RedisKey.USER_TOKEN_STRING, uid);
        String token = RedisUtils.getStr(key);
        if (StrUtil.isNotBlank(token)) {
            return token;
        }
        //获取用户token
        token = jwtUtils.createToken(uid);
        RedisUtils.set(key, token, TOKEN_EXPIRE_DAYS, TimeUnit.DAYS);//token过期用redis中心化控制，初期采用5天过期，剩1天自动续期的方案。后续可以用双token实现
        return token;
    }

    @Override
    public Long getValidUid(String token) {
        boolean verify = verify(token);
        Long uid = verify ? jwtUtils.getUidOrNull(token) : null;
        log.info("getUid-->{}", uid);
        return uid;
    }


    @Override
    public void register(UserDto userDto) {
        // 注册用户信息
        User user = userMapstruct.toEntity(userDto);
        // 设置密码
        user.setPassword(passwordEncoder.encode(user.getPassword()));
        save(user);
    }

}
